#!/usr/bin/env python
# -*- coding: utf-8 -*-
# $Id$

"""
Main GUI app
"""

from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import SIGNAL, SLOT, QObject, QVariant

import sidialogs
import sigui_rc
import sirun

VENDOR_NAME = "pysamizdat"
APP_NAME = "pysamizdat"

# settings flags
APP_POS = 'gui/position'
APP_SIZE = 'gui/size'

GUI_MIN_ON_CLOSE = 'settings/MinOnClose'
GUI_MIN_TO_TRAY = 'settings/MinToTray'
GUI_SHOW_PROGRESS = 'settings/ShowProgress'
GUI_AUTOHIDE_PROGRESS = 'settings/HideOnComplete'

CLI_PARAMS = 'settings/CliParams'

ONSTART_NEWS = 'settings/OnstartNews'
ONSTART_AUTO = 'settings/OnstartAuto'
ONSTART_FORCE = 'settings/OnstartForce'

PERIOD_NEWS = 'settings/PeriodNews' # 20 minutes
PERIOD_AUTO = 'settings/PeriodAuto'  # 6 hours
PERIOD_FORCE = 'settings/PeriodForce' # 7 days

INET_WAIT = 'settings/InetWait'
INET_REPEAT = 'settings/InetRepeat'

UPDATE_HTML = 'settings/UpdateHtml'

CHECK_LASTRUN = 'check/lastrun'
CHECK_NEWSDATE = 'check/newsdate' # last processed news date (for xml news parsing)

# counters on-exit save
CHECK_LASTFORCE = 'check/last-force'
CHECK_LASTAUTO = 'check/last-auto'
CHECK_LASTNEWS = 'check/last-news'

class SIApp(QtGui.QWidget):
	def __init__(self, parent=None):
		QtGui.QWidget.__init__(self, parent)

		screen = QtGui.QDesktopWidget().availableGeometry()
		sz = self.geometry()
		sz = QtCore.QRect(screen.width()-202, screen.y(), 200, 320)
		self.setGeometry(sz) #FIXME: call move/resize after show and qApp.processEvents(...all, 200)

		self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, VENDOR_NAME, APP_NAME)

		self.tmr = QtCore.QTimer()
		self.runner = sirun.CLIRunner()

		self.initIcons()
		self.initActions()
		self.initMenus()
		self.initTray()

		self.connect(QtGui.qApp, QtCore.SIGNAL('aboutToQuit()'), self.onAboutToQuit)
		self.connect(self.tmr, QtCore.SIGNAL('timeout()'), self.onTimer)

		self._newsdate = QtCore.QDateTime()
		self._updHTML = True

		# ***************** #
		self.loadSettings()
		# ***************** #

		self._in_init = True
		self.retranslateUi()
		self._in_init = False

		# check flags and time counters
		self._no_inet = False
		self._disabled = False
		self._fail = False
		self._has_news = False

		self._inet_timer = 0
		self._inet_repeat = 0

		# checking timers
		self.reloadTimes()
		if self._onstart_news: self._secs_news = 0
		if self._onstart_auto: self._secs_auto = 0
		if self._onstart_force: self._secs_force = 0


		# display icon and start timer
		self.tray.show()
		self.tmr.start(1000) # every second


	def reloadTimes(self):
		"""
		load time interval swettings
		"""

		self._lastrun = self.settings.value(CHECK_LASTRUN, QtCore.QVariant(QtCore.QDateTime())).toDateTime()
		n = self._lastrun.secsTo(QtCore.QDateTime().currentDateTime())

		#print "LastRun:",self._lastrun, "==>", n, "seconds elapsed"

		self._secs_news = self.settings.value(CHECK_LASTNEWS,
								QtCore.QVariant(self.settings.value(PERIOD_NEWS, QtCore.QVariant(30)).toInt()[0] * 60) # in seconds
								).toInt()[0] - n

		self._secs_auto = self.settings.value(CHECK_LASTAUTO,
								QtCore.QVariant(self.settings.value(PERIOD_AUTO, QtCore.QVariant(3)).toInt()[0] * 3600) # in seconds
								).toInt()[0] - n
		self._secs_force = self.settings.value(CHECK_LASTFORCE,
								QtCore.QVariant(self.settings.value(PERIOD_FORCE, QtCore.QVariant(2)).toInt()[0] * 86400) # in seconds
								).toInt()[0] - n
		self._onstart_news = self.settings.value(ONSTART_NEWS, QtCore.QVariant(True)).toBool()
		self._onstart_auto = self.settings.value(ONSTART_AUTO, QtCore.QVariant(True)).toBool()
		self._onstart_force = self.settings.value(ONSTART_FORCE, QtCore.QVariant(False)).toBool()


	def retranslateUi(self):
		#if not self._ini_init: self.ui.retranslateUi(self) # future fix: for Qt Designer ui
		self.setWindowTitle(self.tr('Samizdat notifier'))
		# actions

		self.acCheck.setText(self.tr('C&heck now'))
		self.acSettings.setText(self.tr('&Settings ...'))
		self.acExit.setText(self.tr('E&xit'))


	def loadSettings(self):
		# main window
		# ...
		# other parameters
		#self.reloadTimes()
		self._autohide_progress = self.settings.value(GUI_AUTOHIDE_PROGRESS, QtCore.QVariant(False)).toBool()
		self._newsdate = self.settings.value(CHECK_NEWSDATE, QtCore.QVariant(self._newsdate)).toDateTime()
		self._updHTML = self.settings.value(UPDATE_HTML, QtCore.QVariant(self._updHTML)).toBool()


	def storeSettings(self):
		#....

		try:
			# main window
			self.settings.setValue(APP_SIZE, QVariant(self.size()))
			self.settings.setValue(APP_POS, QVariant(self.pos()))
			# timestamps & counters
			self.settings.setValue(CHECK_NEWSDATE, QtCore.QVariant(self._newsdate))
			self.settings.setValue(CHECK_LASTNEWS, QtCore.QVariant(self._secs_news))
			self.settings.setValue(CHECK_LASTAUTO, QtCore.QVariant(self._secs_auto))
			self.settings.setValue(CHECK_LASTFORCE, QtCore.QVariant(self._secs_force))

		finally:
			self.settings.sync()

	def initIcons(self):
		self.icons = {}
		self.icons['lr_gray'] = QtGui.QIcon(':/img/libru_gr.svg')
		self.icons['lr_gray_cross'] = QtGui.QIcon(':/img/libru_gr_cross.svg')
		self.icons['lr_green'] = QtGui.QIcon(':/img/libru_g.svg')
		self.icons['lr_red'] = QtGui.QIcon(':/img/libru_r.svg')
		self.icons['lr_white'] = QtGui.QIcon(':/img/libru_w.svg')
		self.icons['lr_yellow'] = QtGui.QIcon(':/img/libru_y.svg')
		self.icons['lr_blue'] = QtGui.QIcon(':/img/libru_b.svg')

		self.setWindowIcon(self.icons['lr_yellow'])

	def _pix_icon(self, name):
		i = QtGui.QIcon()
		p = QtGui.QPixmap(name)

		if p.isNull() and name and name[0]==":":
			try:
				p = QtGui.QPixmap(name[2:])
			except:
				pass

		i.addPixmap(p, QtGui.QIcon.Normal, QtGui.QIcon.Off)
		return i

	def initActions(self):
		self.acToggleVisible = QtGui.QAction(self) # Hide
		QObject.connect(self.acToggleVisible, SIGNAL("triggered()"), self.onToggleVisible)

		self.acCheck = QtGui.QAction(self) # 'C&heck now'
		self.acCheck.setIcon(self._pix_icon(':/img/checknow.png'))
		QObject.connect(self.acCheck, SIGNAL("triggered()"), self.onCheck)

		self.acSettings = QtGui.QAction(self) # '&Settings ...'
		self.acSettings.setIcon(self._pix_icon(':/img/settings.png'))
		QObject.connect(self.acSettings, SIGNAL("triggered()"), self.onSettings)

		self.acExit = QtGui.QAction(self) # E&xit
		self.acExit.setIcon(self._pix_icon(':/img/quit.png'))
		QObject.connect(self.acExit, SIGNAL("triggered()"), self.onExit)

	def initMenus(self):
		self.trayMenu = QtGui.QMenu(self)
		self.trayMenu.addAction(self.acToggleVisible)
		self.trayMenu.addSeparator()

		self.trayMenu.addAction(self.acCheck)
		self.trayMenu.addAction(self.acSettings)

		self.trayMenu.addSeparator()
		self.trayMenu.addAction(self.acExit)

	def initTray(self):
		"""
		Setup tray icon.

		Tray icon state colors:
			* *Ok*: lr_blue
			* *No-inet*: lr_gray
			* *Disabled*: lr_gray_cross
			* *Checking*: lr_green
			* *Failure*: lr_yellow
			* *News*: lr_red
		"""
		self.tray = QtGui.QSystemTrayIcon(self)
		self.tray.setIcon(self.icons['lr_blue'])
		self.tray.setContextMenu(self.trayMenu)
		#self.tray.setToolTip(QtCore.QCoreApplication.translate('SIApp','Samizdat notifier'))

		QObject.connect(self.tray, SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), self.onTray)

	# === Support functions ===

	def _check_inet(self):
		"""
		"""
		self._no_inet = not sirun.check_inet()
		return not self._no_inet

	# === Event overrides ===
	def changeEvent(self, event):
		"""
		Override for self re-translation support
		"""
		# language changed: retransalte self
		if event == QtCore.QEvent.LanguageChange:
			self.retranslateUi()
			event.accept()
		else:
			QtGui.QWidget.changeEvent(self, event)

	def closeEvent(self, event):
		"""
		Allow hiding main window w/o closing app
		"""
		#TODO: add extra logic

		if self.settings.value(GUI_MIN_ON_CLOSE, QVariant(True)).toBool():
			event.ignore()
			if self.isVisible():
				self.hide()
		#else:
		#	QtGui.QWidget.closeEvent(self, event)

	def hideEvent(self, event):
		if self.settings.value(GUI_MIN_TO_TRAY, QVariant(True)).toBool():
			self.hide()
			event.ignore()
		else:
			QtGui.QWidget.hideEvent(self, event)



	# === Event hanlers ===
	def onExit(self):
		if self.tray: self.tray.hide()
		#TODO: other cleanup
		QtGui.qApp.quit()

	def onAboutToQuit(self):
		if self.tray: self.tray.hide()
		self.storeSettings()

	def onToggleVisible(self):
		if self.isVisible():
			self.hide()
		else:
			self.show()

	def onTray(self, reason):
		if reason == self.tray.Trigger:
			self.onToggleVisible()

		if self.isVisible(): self.acToggleVisible.setText(self.tr('Hide'))
		else: self.acToggleVisible.setText(self.tr('Show'))


	def onSettings(self):
		dlg = sidialogs.SetupDialog(self)
		self.settings.beginGroup('settings')
		try:
			# pre-fill dialog
			for ob, ty in dlg.fieldList:
				nm = ob.objectName()[len(ty)+1:]
				if ty == 'i':
					x=self.settings.value(nm, QVariant(ob.value())).toInt()
					if x[1]: ob.setValue(x[0])
				elif ty == 'cb': ob.setChecked(self.settings.value(nm, QVariant(ob.isChecked())).toBool())
				elif ty == 's': ob.setText(self.settings.value(nm, QVariant(ob.text())).toString())

			dlg.show()
			dlg.center()

			if dlg.exec_():
				for ob, ty in dlg.fieldList:
					nm = ob.objectName()[len(ty)+1:]
					#print ty, nm
					if ty == 'i': self.settings.setValue(nm, QVariant(ob.value()))
					elif ty == 'cb': self.settings.setValue(nm, QVariant(ob.isChecked()))
					elif ty == 's': self.settings.setValue(nm, QVariant(ob.text()))

		except Exception, e:
				print >> sys.stderr, "onSettings exception: %s" % (e,)
		finally:
			self.settings.endGroup()
			self.settings.sync()
			self.reloadTimes()

	# --- checking handlers ---
	def onTimer(self):
		# check schedule times

		# Inet-check: called only from onCheck()
		if self._inet_timer:
			self._inet_timer -= 1
			if self._inet_timer <= 0:
				self._check_inet()
				if self._no_inet:
					if self._inet_repeat >0:
						self._inet_repeat -= 1
						self._inet_timer = self.settings.value(INET_WAIT, QtCore.QVariant(90)).toInt()[0]
					else: # all timers are reset
						pass
				else:
					self._inet_repeat = self._inet_timer = 0
					self.onCheck()

		#TODO: checking schedule events
		curr = QtCore.QDateTime.currentDateTime()
		if not self._lastrun:
			self._lastrun = QtCore.QDateTime()

		if self._lastrun.isNull():
			self._lastrun = curr

		#n = self._lastrun.secsTo(curr)
		print "counters (%d, %d, %d)\r" % (self._secs_force, self._secs_auto, self._secs_news),

		if not (self.runner and self.runner.isRunning):
			self._secs_force -= 1
			self._secs_auto -= 1
			self._secs_news -= 1

		if self._secs_auto<=0 or self._secs_force<=0 or self._secs_news<=0:
			print " **** CHECK ****"
			self.onCheck()

		# autohide progress window
		if self._autohide_progress and not self.runner.isRunning and self.runner.dlg.isVisible():
			self.runner.dlg.hide()

		# update icons
		if self.runner and self.runner.isRunning:
			if self.tray.icon() != self.icons['lr_green']: self.tray.setIcon(self.icons['lr_green'])
			self.tray.setToolTip(self.tr('Checker is running'))
		else:
			s = 'lr_blue' # ok, ready
			n = min(self._secs_auto, self._secs_force, self._secs_news) #- self._lastrun.secsTo(QtCore.QDateTime.currentDateTime())
			dnext = QtCore.QDateTime.currentDateTime().addSecs(n)
			msg = unicode(self.tr('Ready.\nNext check at %(date)s')) % {'date':unicode(dnext.toString('dd.MM.yyyy hh:mm:ss'))}

			if self._no_inet:
				s = 'lr_gray'
				msg = self.tr('No internet connection')
			if self._disabled:
				s = 'lr_gray_cross'
				msg = self.tr('Temporary disabled')
			if self._fail:
				s = 'lr_yellow'
				msg = msg = self.tr('Failure')
			if self._has_news:
				s = 'lr_red'
				msg = self.tr('News found.')

			if self.tray.icon() != self.icons[s]: self.tray.setIcon(self.icons[s])

			self.tray.setToolTip(msg)



	def onCheck(self):
		if not self.runner:
			self.runner = sirun.CLIRunner()

		show_flag = self.settings.value(GUI_SHOW_PROGRESS, QtCore.QVariant(True)).toBool()

		if self.runner.isRunning:
			self.runner.dlg.show()
			self._lastrun = QtCore.QDateTime.currentDateTime()
			self.settings.setValue(CHECK_LASTRUN, QtCore.QVariant(self._lastrun))
		else:
			#TODO: set workdir, add extra switches, add checking mode switches(?)
			#self.runner.workdir = self.settings.value('app path') - path to samizdat.py

			# 1. try check i-net if needed
			if self.settings.value(INET_REPEAT, QtCore.QVariant(0)).toInt()[0] > 0:
				if self._inet_timer > 0 or self._inet_repeat > 0:
					#TODO: may be more checks
					return
				if not self._check_inet():
					self._inet_repeat = self.settings.value(INET_REPEAT, QtCore.QVariant(3)).toInt()[0]
					self._inet_timer = self.settings.value(INET_WAIT, QtCore.QVariant(90)).toInt()[0]
					#TODO: may be more actions on no-inet
					return

			args = ['samizdat.py', '--progress']

			# append checking flags
			#n = self._lastrun.secsTo(QtCore.QDateTime.currentDateTime())

			print "CHECK [%s %s %s]" % (self._secs_force, self._secs_auto, self._secs_news)

			# force -> auto -> news_or_manual
			if self._secs_force <= 0: # force mode allowed
				args.append('--ignore-lastmod')
				self._secs_force = self.settings.value(PERIOD_FORCE, QtCore.QVariant(2)).toInt()[0] * 86400 # in seconds
				self._secs_auto = self.settings.value(PERIOD_AUTO, QtCore.QVariant(3)).toInt()[0] * 3600 # in seconds
				self._secs_news = self.settings.value(PERIOD_NEWS, QtCore.QVariant(30)).toInt()[0] * 60 # in seconds
				print " !!! Force check !!!"
			elif self._secs_auto <= 0: # auto mode allowed
				self._secs_auto = self.settings.value(PERIOD_AUTO, QtCore.QVariant(3)).toInt()[0] * 3600 # in seconds
				self._secs_news = self.settings.value(PERIOD_NEWS, QtCore.QVariant(30)).toInt()[0] * 60 # in seconds
			else: # by-default only news mode allowed
				args.append('--check-csv')
				self._secs_news = self.settings.value(PERIOD_NEWS, QtCore.QVariant(30)).toInt()[0] * 60 # in seconds

			if self._updHTML:
				args.append('--save-htm-news=yes')
			else:
				args.append('--save-htm-news=no')

			try:
				s = str(self.settings.value(CLI_PARAMS, QtCore.QVariant('')).toString()).split(' ')
				if s:
					args.extend(s)
			except:
				pass

			self.runner.run('python', args, show_flag)

			self._lastrun = QtCore.QDateTime.currentDateTime()
			self.settings.setValue(CHECK_LASTRUN, QtCore.QVariant(self._lastrun))


if __name__=="__main__":
	import sys

	app = QtGui.QApplication(sys.argv)
	app.setQuitOnLastWindowClosed(False)
	w = SIApp()

	if not w.settings.value("settings/StartMin",QVariant(False)).toBool():
		w.show()

	# decorated window layouts fix:
	# wait for windows displayed by layout manager, then move/resize to saved position
	app.processEvents(QtCore.QEventLoop.AllEvents|QtCore.QEventLoop.WaitForMoreEvents, 250) # 250ms wait for Layout manager
	w.resize(w.settings.value(APP_SIZE, QtCore.QVariant(w.size())).toSize())
	w.move(w.settings.value(APP_POS, QtCore.QVariant(w.pos())).toPoint())

	app.exec_()
